use std::collections::*;
use std::sync::Arc;

use craftdig_math::*;

use super::editable;
use super::view::WorldView;
use crate::block::*;
use crate::chunk::ChunkSlotmap;
use crate::entity::{EntityId, EntityState};

/// Complete chunk data.
#[derive(Clone, Debug)]
pub struct ChunkState {
    blocks: ChunkSlotmap<BlkId>,
}

impl ChunkState {
    /// Creates a new chunk full of a single type of block.
    pub fn new_full(id: BlkId) -> Self {
        Self {
            blocks: ChunkSlotmap::with_all(id),
        }
    }

    /// Returns the block ID for the subchunk pos provided.
    pub fn get_block_id(&self, pos: SubchunkPos) -> BlkId {
        *self.blocks.get(pos)
    }

    /// Sets the block state from a state snapshot.
    pub fn set_block_state(&mut self, pos: SubchunkPos, state: &BlockStateSnapshot) {
        *self.blocks.get_mut(pos) = state.id();
    }
}

/// Snapshot of world state.
#[derive(Clone)]
pub struct WorldState {
    chunks: HashMap<ChunkCoord, Arc<ChunkState>>,
    entities: BTreeMap<EntityId, Arc<EntityState>>,
}

impl WorldState {
    pub(crate) fn new(
        chunks: HashMap<ChunkCoord, Arc<ChunkState>>,
        entities: BTreeMap<EntityId, Arc<EntityState>>,
    ) -> Self {
        Self { chunks, entities }
    }

    /// Creates a new world state with a single empty chunk.
    pub fn new_empty() -> Self {
        let chunk = ChunkState::new_full(BlkId::zero());
        let mut chunks = HashMap::new();
        chunks.insert(ChunkCoord::from(Vec3i::zero()), Arc::new(chunk));
        Self::new(chunks, BTreeMap::new())
    }

    pub fn chunks_iter(&self) -> impl Iterator<Item = (ChunkCoord, &Arc<ChunkState>)> {
        self.chunks.iter().map(|(cc, cs)| (*cc, cs))
    }

    pub fn get_chunk(&self, coord: ChunkCoord) -> Option<&Arc<ChunkState>> {
        self.chunks.get(&coord)
    }

    pub fn get_entity_state(&self, eid: &EntityId) -> Option<&Arc<EntityState>> {
        self.entities.get(eid)
    }

    pub fn get_block_id(&self, coord: BlkCoord) -> Option<BlkId> {
        let (cc, scp) = conv_world_pos_to_chunk(coord);
        let chunk = self.get_chunk(cc)?;
        Some(chunk.get_block_id(scp))
    }

    /// Finalizes the edited world state into a new world state and returns a
    /// summary of chagnes.
    pub fn apply_diff(&self, mut diff: editable::Diff) -> (WorldState, editable::TickChanges) {
        let mut chunks = HashMap::new();
        let mut entities = BTreeMap::new();
        let mut changed_chunks = Vec::new();
        let mut changed_entities = Vec::new();

        // Chunks that are still here but are being changed.
        for (cc, cs) in self.chunks_iter() {
            if let Some(new_state) = diff.dirty_chunks.remove(&cc) {
                chunks.insert(cc, Arc::new(new_state));
                changed_chunks.push(cc);
            } else {
                chunks.insert(cc, cs.clone());
            }
        }

        // New chunks that are being added.
        for (cc, cs) in diff.dirty_chunks {
            chunks.insert(cc, Arc::new(cs));
            changed_chunks.push(cc);
        }

        // Updated entities.
        for (eid, estate) in self.entities.iter() {
            if let Some(new_state) = diff.dirty_entities.remove(eid) {
                entities.insert(*eid, Arc::new(new_state));
                changed_entities.push(*eid);
            } else {
                entities.insert(*eid, estate.clone());
            }
        }

        // New entities that are being added.
        for (eid, estate) in diff.dirty_entities {
            entities.insert(eid, Arc::new(estate));
            changed_entities.push(eid);
        }

        let chgs = editable::TickChanges::new(changed_chunks, changed_entities);
        (Self::new(chunks, entities), chgs)
    }
}

impl WorldView for WorldState {
    fn get_block_id(&self, coord: BlkCoord) -> Option<BlkId> {
        // Don't call it as the trait function.
        WorldState::get_block_id(self, coord)
    }

    fn get_chunk_ref(&self, coord: ChunkCoord) -> Option<&ChunkState> {
        WorldState::get_chunk(self, coord).map(Arc::as_ref)
    }

    fn get_entity_state(&self, eid: &EntityId) -> Option<&EntityState> {
        WorldState::get_entity_state(self, eid).map(Arc::as_ref)
    }

    fn get_entity_ids(&self) -> Vec<EntityId> {
        self.entities.keys().copied().collect::<Vec<_>>()
    }
}

impl<'w> WorldView for &'w WorldState {
    fn get_block_id(&self, coord: BlkCoord) -> Option<BlkId> {
        // Don't call it as the trait function.
        WorldState::get_block_id(self, coord)
    }

    fn get_chunk_ref(&self, coord: ChunkCoord) -> Option<&ChunkState> {
        WorldState::get_chunk(self, coord).map(Arc::as_ref)
    }

    fn get_entity_state(&self, eid: &EntityId) -> Option<&EntityState> {
        WorldState::get_entity_state(self, eid).map(Arc::as_ref)
    }

    fn get_entity_ids(&self) -> Vec<EntityId> {
        self.entities.keys().copied().collect::<Vec<_>>()
    }
}
